Skip to main content

Mobile — Examples

Complete working examples for mobile Aspects. Each example runs in a WebView as an .html document with a regular DOM (head/body). They demonstrate mobile-specific patterns (HTML wrapper, native bridges, window negotiation) and shared patterns such as optional Shadow DOM for style isolation when needed.

For background on the categories (context-less vs. context-aware), see the Aspects Overview. For a side-by-side comparison with web patterns, see the Mobile Technical Reference.


Context-less: Notification Banner

A simple mobile Aspect that displays a notification banner inside the WebView and reports its layout to the native app. No user context or authentication is needed.

Patterns demonstrated: HTML wrapper, meta tag injection, sizeAndLocation bridge.

<html>
<head></head>
<body>
<script type="text/javascript">
// Meta tags
var meta1 = document.createElement('meta');
meta1.setAttribute('charset', 'utf-8');
document.head.appendChild(meta1);

var meta2 = document.createElement('meta');
meta2.setAttribute('name', 'viewport');
meta2.setAttribute('content', 'width=device-width, initial-scale=1, shrink-to-fit=no');
document.head.appendChild(meta2);

// Create banner
var banner = document.createElement('div');
banner.id = 'notification-banner';
banner.textContent = 'Welcome! Check out our new savings rates.';
banner.style.cssText =
'background-color:#1a73e8;color:#ffffff;text-align:center;' +
'padding:12px 20px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;' +
'font-size:14px;position:fixed;top:0;left:0;width:100%;z-index:9999;';
document.body.appendChild(banner);

// Report layout to native app
var bannerRect = banner.getBoundingClientRect();
var aspectDetails = {
aspectLocations: [{
x: '0',
y: '0',
width: '' + window.screen.width,
height: '' + Math.ceil(bannerRect.height * 1.25),
webWidth: '' + window.screen.width,
webHeight: '' + window.screen.height
}]
};
var jsonString = JSON.stringify(aspectDetails);
if (navigator.userAgent && navigator.userAgent.match(/(iPad|iPhone|iPod)/i)) {
window.webkit.messageHandlers.sizeAndLocation.postMessage(jsonString);
} else {
JSBridge.sizeAndLocation(jsonString);
}
</script>
</body>
</html>

Context-aware: Third-Party Widget with Native Bridge Authentication

A full mobile Aspect that integrates a third-party vendor widget. This is the most common real-world pattern for mobile Aspects and demonstrates every mobile-specific concept.

Patterns demonstrated:

  • HTML wrapper with meta tag injection
  • Native bridge authentication (tokenApiDetailsapiToken:ready)
  • Manual script loading (document.createElement('script'))
  • Optional Shadow DOM for style isolation
  • Window size negotiation (condenseWindow / expandWindow)
  • MutationObserver for widget state tracking
  • waitForElement for async element detection
<html>
<head></head>
<body>
<script type="text/javascript">
// ============== META TAGS ==============
var meta1 = document.createElement('meta');
meta1.setAttribute('charset', 'utf-8');
document.head.appendChild(meta1);

var meta2 = document.createElement('meta');
meta2.setAttribute('name', 'viewport');
meta2.setAttribute('content', 'width=device-width, initial-scale=1, shrink-to-fit=no');
document.head.appendChild(meta2);

// ============== VENDOR CONFIGURATION ==============
// Replace these values with your vendor's actual configuration
var VENDOR_CONFIG = {
scriptUrl: 'https://cdn.your-vendor.com/sdk/v3/widget.js',
containerId: 'vendor-widget',
launcherSelector: 'div.vendor-launcher',
visibilityAttr: 'data-visible'
};

// ============== WIDGET CONTAINER (Shadow DOM) ==============
var widgetDiv = document.createElement('div');
widgetDiv.id = VENDOR_CONFIG.containerId;
var shadowRoot = widgetDiv.attachShadow({ mode: 'open' });
shadowRoot.innerHTML = '<div></div>';
document.body.appendChild(widgetDiv);

// ============== STYLES ==============
var css = '#' + VENDOR_CONFIG.containerId +
' { z-index: 999999 !important; }';
var styleEl = document.createElement('style');
styleEl.appendChild(document.createTextNode(css));
document.head.appendChild(styleEl);

// ============== NATIVE BRIDGE: AUTHENTICATION ==============
// Send API call details to the native app for execution
var apiCallDetails = {
method: 'GET',
headers: { cookie: '' },
url: 'https://<FI_DOMAIN>/feng-bff/live/v1/aspect/token?clientId=<YOUR_CLIENT_ID>'
};

var apiCallString = JSON.stringify(apiCallDetails);
if (navigator.userAgent && navigator.userAgent.match(/(iPad|iPhone|iPod)/i)) {
window.webkit.messageHandlers.tokenApiDetails.postMessage(apiCallString);
} else {
JSBridge.tokenApiDetails(apiCallString);
}

// Wait for the native app to deliver the auth token
function getAuthToken() {
if (window.dbk && window.dbk.tokenResponse) {
return Promise.resolve(window.dbk.tokenResponse);
}
return new Promise(function (resolve) {
var onReady = function () {
window.removeEventListener('apiToken:ready', onReady);
resolve(window.dbk.tokenResponse);
};
window.addEventListener('apiToken:ready', onReady);
});
}

// ============== NATIVE BRIDGE: WINDOW SIZE ==============
var chatButton;
var chatButtonLocation;

function resizeWindow(aspectDetails) {
var jsonString = JSON.stringify(aspectDetails);
if (navigator.userAgent && navigator.userAgent.match(/(iPad|iPhone|iPod)/i)) {
window.webkit.messageHandlers.sizeAndLocation.postMessage(jsonString);
} else {
JSBridge.sizeAndLocation(jsonString);
}
}

function condenseWindow() {
try {
var aspectDetails = {
aspectLocations: [{
x: '' + chatButtonLocation.x,
y: '' + chatButtonLocation.y,
width: '' + (chatButtonLocation.width * 1.25),
height: '' + (chatButtonLocation.height * 1.25),
webWidth: '' + window.screen.width,
webHeight: '' + window.screen.height
}]
};
resizeWindow(aspectDetails);
} catch (ex) {
console.error('Condense window error:', ex);
}
}

function expandWindow() {
try {
var aspectDetails = {
aspectLocations: [{
x: '0',
y: '0',
width: '' + window.screen.width,
height: '' + window.screen.height,
webWidth: '' + window.screen.width,
webHeight: '' + window.screen.height
}]
};
resizeWindow(aspectDetails);
} catch (ex) {
console.error('Expand window error:', ex);
}
}

// ============== ASYNC ELEMENT DETECTION ==============
function waitForElement(root, selector, timeout) {
timeout = timeout || 10000;
return new Promise(function (resolve, reject) {
var element = root.querySelector(selector);
if (element) { resolve(element); return; }

var observer = new MutationObserver(function (mutations) {
for (var i = 0; i < mutations.length; i++) {
var added = mutations[i].addedNodes;
for (var j = 0; j < added.length; j++) {
if (added[j].nodeType === Node.ELEMENT_NODE) {
var found = root.querySelector(selector);
if (found) {
observer.disconnect();
clearTimeout(timeoutId);
resolve(found);
return;
}
}
}
}
});
observer.observe(root, { childList: true, subtree: true });

var timeoutId = setTimeout(function () {
observer.disconnect();
reject(new Error('Element not found: ' + selector));
}, timeout);
});
}

// ============== WIDGET STATE OBSERVATION ==============
function setUp(launcherElement) {
chatButton = launcherElement;
chatButtonLocation = chatButton.getBoundingClientRect();

// Find the vendor's root element inside Shadow DOM
var vendorRoot = document.querySelector('#' + VENDOR_CONFIG.containerId)
.shadowRoot.querySelector('div.vendor-root');

// Observe open/close state changes
var stateObserver = new MutationObserver(function (mutationsList) {
for (var i = 0; i < mutationsList.length; i++) {
var mutation = mutationsList[i];
if (mutation.type === 'attributes') {
var isVisible = mutation.target.getAttribute(
mutation.attributeName) === 'true';
if (isVisible) {
expandWindow();
} else {
condenseWindow();
}
}
}
});

stateObserver.observe(vendorRoot, {
attributes: true,
attributeFilter: [VENDOR_CONFIG.visibilityAttr],
attributeOldValue: true
});

// Start in condensed state
condenseWindow();
}

// ============== SCRIPT LOADING AND INITIALIZATION ==============
var script = document.createElement('script');
script.src = VENDOR_CONFIG.scriptUrl;
script.type = 'text/javascript';
script.onload = function () {
try {
// Initialize vendor SDK (replace with your vendor's API)
var widget = new VendorSDK({
authenticate: getAuthToken
});

} catch (error) {
console.error('Failed to initialize vendor SDK:', error);
}
};
script.onerror = function () {
console.error('Failed to load vendor script');
};
document.body.appendChild(script);

// Wait for the vendor launcher to appear, then set up state observation
var hostElement = document.querySelector('#' + VENDOR_CONFIG.containerId);
waitForElement(hostElement.shadowRoot, VENDOR_CONFIG.launcherSelector)
.then(function (launcherElement) {
setUp(launcherElement);
})
.catch(function (error) {
console.error('Error:', error);
});
</script>
</body>
</html>
tip

Replace VENDOR_CONFIG values with your actual vendor's script URL. The VendorSDK constructor, launcherSelector, and visibilityAttr must match your specific vendor's JavaScript SDK. Check the vendor's documentation for the correct selectors and state attributes.


Side-by-Side: Web vs. Mobile for the Same Integration

The table below shows how each step of a third-party widget integration maps between platforms. Use this when you need to build both a web and mobile Aspect for the same vendor.

StepWeb AspectMobile Aspect
File format.js file.html document with <script> block
Platform guardisRunningInWebView() check at top — throws if in WebViewNo guard needed
Meta tagsNot needed (host page provides them)Inject programmatically at start of script
Request authfetch(tokenUrl, { Cookie: document.cookie })tokenApiDetails bridge → native app executes
Receive authfetch() Promise resolves with response JSONListen for apiToken:ready event on window
Load vendor scriptdbk.loadScript(url)document.createElement('script') with onload
LayoutCSS handles positioningReport bounding box via sizeAndLocation bridge
Detect open/closeNot needed (CSS transitions handle it)MutationObserver on vendor's visibility attribute
On openNo action neededCall expandWindow() → full-screen WebView
On closeNo action neededCall condenseWindow() → button-sized WebView
Wait for elementsNot needed (direct DOM access)waitForElement() utility with MutationObserver

Next Steps